package com.gmail.dengtao.joe.group.interceptor;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.gmail.dengtao.joe.group.channel.Channel;
import com.gmail.dengtao.joe.group.channel.ChannelListener;
import com.gmail.dengtao.joe.group.interceptor.impl.TailInterceptor;
import com.gmail.dengtao.joe.group.member.IdleStatus;
import com.gmail.dengtao.joe.group.member.Member;
import com.gmail.dengtao.joe.group.member.MembershipListener;
import com.gmail.dengtao.joe.transport.handler.Handler;
import com.gmail.dengtao.joe.transport.session.Session;

public class InterceptorStack {
	
	/** interceptor name to interceptor */
	private Map<String, InterceptorEntity> interceptorEntities = new ConcurrentHashMap<String, InterceptorEntity>();
	/** The stack head */
    private InterceptorEntity head;
    /** The stack tail */
    private InterceptorEntity tail;
    /** The tail interceptor */
    private TailInterceptor tailInterceptor;
   
	public InterceptorStack() {
		head = new InterceptorEntity(null, null, "head", null);
		tailInterceptor = new TailInterceptor();
        tail = new InterceptorEntity(head, null, "tail", tailInterceptor);
        head.setNextEntity(tail);
	}

	public void setMembershipListener(MembershipListener membershipListener) {
		tailInterceptor.setMembershipListener(membershipListener);
	}

	public void setChannelListener(ChannelListener channelListener) {
		tailInterceptor.setChannelListener(channelListener);
	}
	
	/**
	 * Adds the specified interceptor with the specified name at the beginning of this stack.
	 * @param name The interceptor's name
     * @param interceptor The interceptor to add
     * @throws IllegalArgumentException
	 * @since 1.0
	 */
	public synchronized void addFirst(String name, Interceptor interceptor) throws IllegalArgumentException {
		checkAddable(name);
		register(head, name, interceptor);
	}

	/**
	 * Adds the specified interceptor with the specified name at the end of this stack.
	 * 
	 * @param name The interceptor's name
     * @param interceptor The interceptor to add
     * @throws IllegalArgumentException
     * @since 1.0
	 */
	public synchronized void addLast(String name, Interceptor interceptor) throws IllegalArgumentException {
		checkAddable(name);
		register(tail.getPrevEntity(), name, interceptor);
	}
	
	/**
	 * Replace the specified interceptor with name in this stack.
	 * @param name interceptor name need to be replace
	 * @param interceptor new interceptor
	 * @return true if replace is success
	 * @throws IllegalArgumentException the specified interceptor name is not registered in this stack.
	 * @since 1.0
	 */
	public synchronized boolean replace(String name, Interceptor interceptor) throws IllegalArgumentException {
		if (interceptor == null) {
			throw new IllegalArgumentException("interceptor is null.");
		}
		InterceptorEntity entity = checkOldName(name);
		if (entity.getInterceptor() != null) {
			entity.getInterceptor().destroy();
		}
        entity.setInterceptor(interceptor);
        interceptor.init();
        return true;
    }
	
	/**
	 * Remove the specified interceptor with name in this stack
	 * @throws IllegalArgumentException the specified interceptor name is not registered in this stack.
	 * @param name
	 * @return true if remove is success
	 * @since 1.0
	 */
	public synchronized boolean remove(String name) throws IllegalArgumentException {
		InterceptorEntity entity = checkOldName(name);
        deregister(entity);
        return true;
    }
	
	/**
     * Throws an exception when the specified interceptor name is not registered in this stack.
     *
     * @throws IllegalArgumentException the specified interceptor name is not registered in this stack.
     * @return An interceptor entity with the specified name.
     * @since 1.0
     */
    private InterceptorEntity checkOldName(String baseName) throws IllegalArgumentException {
    	InterceptorEntity e = interceptorEntities.get(baseName);
        if (e == null) {
            throw new IllegalArgumentException("interceptor not found:" + baseName);
        }
        return e;
    }
    
    /**
     * Deregister FilterEntity to this FilterChain
     * @param entity entity to be removed
     * @since 1.0
     */
	private void deregister(InterceptorEntity entity) {
		InterceptorEntity prevEntry = entity.getPrevEntity();
		InterceptorEntity nextEntry = entity.getNextEntity();
        prevEntry.setNextEntity(nextEntry);
        nextEntry.setPrevEntity(prevEntry);
        interceptorEntities.remove(entity.getName());
        if (entity.getInterceptor() != null) {
        	entity.getInterceptor().destroy();
	 	}
	}
	
	/**
     * Regitster FilterEntity to this FilterChain
     * @param prevEntry
     * @param name filter name
     * @param interceptor filter instances
     * @since 1.0
     */
	private void register(InterceptorEntity prevEntry, String name, Interceptor interceptor) {
		if (interceptor == null) {
			throw new IllegalArgumentException("interceptor is null.");
		}
		InterceptorEntity newInterceptorEntry = new InterceptorEntity(prevEntry, prevEntry.getNextEntity(), name, interceptor);
	 	prevEntry.getNextEntity().setPrevEntity(newInterceptorEntry);
	 	prevEntry.setNextEntity(newInterceptorEntry);
	 	interceptorEntities.put(name, newInterceptorEntry);
	 	if (newInterceptorEntry.getInterceptor() != null) {
	 		newInterceptorEntry.getInterceptor().init();
	 	}
	}
	
	/**
     * Checks the specified interceptor name is already taken and throws an exception if already taken.
     * @param name interceptor name
     * @throws IllegalArgumentException
     */
    private void checkAddable(String name) throws IllegalArgumentException {
        if (interceptorEntities.containsKey(name)) {
            throw new IllegalArgumentException(
                    "Other interceptor is using the same name '" + name + "'");
        }
    }
	
    /**
     * Fires a {@link Handler#memberAdded(member)} event. Most users don't need to
     * call this method at all. Please use this method only when you implement a new transport
     * or fire a virtual event.
     * @param member added member
     * @since 1.0
     */
    public synchronized void fireMemberAdded(Channel channel, Member member) {
    	callNextMemberAdded(head.getNextEntity(), channel, member);
    }
    
    private void callNextMemberAdded(InterceptorEntity interceptorEntity, Channel channel, Member member) {
        try {
        	Interceptor interceptor = interceptorEntity.getInterceptor();
        	interceptor.memberAdded(interceptorEntity.getNextEntity(), channel, member);
        } catch (Throwable e) {
            fireExceptionCaught(channel, member, e);
        }
    }

    /**
     * Fires a {@link Handler#sessionClosed(Session)} event. Most users don't need to call
     * this method at all. Please use this method only when you implement a new transport or
     * fire a virtual event.
     * @param session to be closed
     * @since 1.0
     */
    public synchronized void fireMemberDisappeared(Channel channel, Member member) {
    	callNextMemberDisappeared(head.getNextEntity(), channel, member);
    }

    private void callNextMemberDisappeared(InterceptorEntity interceptorEntity, Channel channel, Member member) {
        try {
        	Interceptor interceptor = interceptorEntity.getInterceptor();
        	interceptor.memberDisappeared(interceptorEntity.getNextEntity(), channel, member);
        } catch (Throwable e) {
            fireExceptionCaught(channel, member, e);
        }
    }
    
    /**
     * Fires a {@link Handler#sessionIdle(Session, IdleStatus)} event. Most users don't need to call
     * this method at all. Please use this method only when you implement a new transport or
     * fire a virtual event.
     * @param session idle session
     * @param status idle status {@link IdleStatus#READER_IDLE} or {@link IdleStatus#WRITER_IDLE}
     * @since 1.0
     */
    public synchronized void fireMemberIdle(Channel channel, Member member, IdleStatus status) {
    	callNextMemberIdle(head.getNextEntity(), channel, member, status);
    }

    private void callNextMemberIdle(InterceptorEntity interceptorEntity, Channel channel, Member member, IdleStatus status) {
        try {
        	Interceptor interceptor = interceptorEntity.getInterceptor();
        	interceptor.memberIdle(interceptorEntity.getNextEntity(), channel, member, status);
        } catch (Throwable e) {
            fireExceptionCaught(channel, member, e);
        }
    }
    
    /**
     * Fires a {@link Handler#exceptionCaught(Session, Throwable)} event. Most users don't
     * need to call this method at all. Please use this method only when you implement a new
     * transport or fire a virtual event.
     * @param session the session whitch exception happened!
     * @param cause The exception cause
     */
    public synchronized void fireExceptionCaught(Channel channel, Member member, Throwable cause) {
        callNextExceptionCaught(head.getNextEntity(), channel, member, cause);
    }

    private void callNextExceptionCaught(InterceptorEntity interceptorEntity, Channel channel, Member member, Throwable cause) {
    	Interceptor interceptor = interceptorEntity.getInterceptor();
    	if (interceptor != null) {
    		interceptor.exceptionCaught(interceptorEntity.getNextEntity(), channel, member, cause);
    	} else {
    		
    	}
    }
    
    /**
     * Fires a {@link Handler#dataReceived(Session, Object)} event. Most users don't need to
     * call this method at all. Please use this method only when you implement a new transport
     * or fire a virtual event.
     * 
     * @param data The received data
     */
    public synchronized void fireDataReceived(Channel channel, Member sender, Object data) {
        callNextDataReceived(head.getNextEntity(), channel, sender, data);
    }

    private void callNextDataReceived(InterceptorEntity interceptorEntity, Channel channel, Member member, Object data) {
        try {
        	interceptorEntity.getInterceptor().dataReceived(interceptorEntity.getNextEntity(), channel, member, data);
        } catch (Throwable e) {
        	fireExceptionCaught(channel, member, e);
        }
    }
    
    /**
     * Fires a {@link Handler#dataSent(Session, Object)} event. Most users don't need to call
     * this method at all. Please use this method only when you implement a new transport or
     * fire a virtual event.
     */
    public synchronized void fireDataSent(Channel channel, Member member, Object data) {
    	callNextDataSent(head.getNextEntity(), channel, member, data);
    }

    private void callNextDataSent(InterceptorEntity interceptorEntity, Channel channel, Member member, Object data) {
        try {
        	interceptorEntity.getInterceptor().dataSent(interceptorEntity.getNextEntity(), channel, member, data);
        } catch (Throwable e) {
        	fireExceptionCaught(channel, member, e);
        }
    }

    /**
     * Fires a {@link Handler#dataSent(Session, Object)} event. Most users don't need to call
     * this method at all. Please use this method only when you implement a new transport or
     * fire a virtual event.
     */
    public synchronized void fireDataNotSent(Channel channel, Member target, Object data) {
    	callNextDataNotSent(head.getNextEntity(), channel, target, data);
    }

    private void callNextDataNotSent(InterceptorEntity interceptorEntity, Channel channel, Member member, Object data) {
        try {
        	interceptorEntity.getInterceptor().dataNotSent(interceptorEntity.getNextEntity(), channel, member, data);
        } catch (Throwable e) {
        	fireExceptionCaught(channel, member, e);
        }
    }
}